<?php
/**
*	Dbm cache handle class
*/
class KVdbm{

	protected 	$cache_name;  	# name of this cache 
	protected 	$h_param;         	# extra parameter
	protected	$handle_obj;		# handle obj
	
	private		$data_file = '';  			# child cannot inherit
	private		$dbm_handler_type = 'db4';  # different PHP will provide different handler
	                                    	# pls note this value can be defined in php.ini
	                                    	# but we have defiend it in site_config.php
	protected	$expire = 0;				# no expiry time, but no guarantee  it stay

	protected	$read_through_mode 	= false; # fild from db when not available. not suitable for sharding table
	protected	$read_only_mode    	= false; #
	protected	$cache_status		= false; # show cache status
	
	function __construct($handle_obj, $h_param = array()){

		global $kvcache;	# get config from KVCacheConfig.php

		$this->handle_obj = $handle_obj;							#外調處理類對象
		$this->expire 	  = $this->handle_obj->get_cache_expire();	#超時時間
		if($this->expire=='-1'){
			$this->expire = get_hash_param($kvcache,'default_expire',0);	
		}
		$this->cache_name = $this->handle_obj->get_cache_name();
		$this->h_param    = $h_param;
		$this->dbm_handler_type = get_hash_param($kvcache, 'default_dbm_handler');
		$cache_path = get_hash_param($kvcache, 'default_dbm_path');

		if(get_hash_param($h_param, 'expire', '-1')!='-1'){		//have set expire  param
			$this->expire = get_hash_param($h_param, 'expire', 0);	//have set expire param	
		}
		$h_config = get_hash_param($kvcache, $this->cache_name, ''); # check if not exists
	
		if ($h_config != ''){	# define config
			$dbm_name = get_hash_param($h_config, 'dbm', '');						# get dbm_name
			if ($dbm_name == ''){
				$dbm_name = $this->cache_name;										# cachename default dbm_name
			}
			$mode = get_hash_param($h_config, 'mode', '');							# get mode
			if($mode == ''){
				$mode = get_hash_param($kvcache, 'default_mode','read-only');		# get default mode;
			}
		} else {	# not define config
			$dbm_name = $this->cache_name;											# cachename default dbm_name
			$mode = get_hash_param($kvcache, 'default_mode','read-only');			# get default mode;
		}

		$this->data_file = $cache_path.$dbm_name.'.dbm';
		if ($mode == 'read-through'){
			$this->read_through_mode = true;
		} else if ($mode == 'read-only'){
			$this->read_only_mode = true;
		}
	}
	
	function create_cache_key($key_in){
		return $this->cache_name.'_'.$key_in;
	}	
	
	# get - a read through
	#       try to get from cache, if not exists, read from db	
	function get($key_in){
		$value = $this->read($key_in);
		$expiretime = $this->read($key_in.'_expire');

		if ($value == '' || ($expiretime<time() && $expiretime!='-1')){		//not value or expire $value === FALSE
			if ($this->read_through_mode == true){	  
				$value = $this->create_cache_content($key_in);
				$this->set($key_in, $value);
			} else {
				$value = '';  # non read-through mode, return ''
			}
			$this->cache_status = false;
		} else {
			#echo "Using GDBM\n";
			$this->cache_status = true;
		}
	
		if ( method_exists($this, 'post_format') ){
			$value = $this->post_format($key_in, $value);
		}
		return $value;
	}
	
	# memcache'd' support get_multi / set multi which has better performance, 
	# so implement in dbm 
	# return as array
	function mget($a_key_in){
		$a_result = array();
		foreach ( $a_key_in as $key_in){			
			array_push ($a_result, $this->get($key_in));
		}
		return $a_result;
	}
	
	/* similar to mget, except it return as associative array*/
	function mget_hash($a_key_in){
		$h_result = array();
		foreach ( $a_key_in as $key_in){
			$h_result[$key_in] = $this->get($key_in);
		}
		return $h_result;		
	}
	
	// cache data (in read-only mode) for later read
	function cache($key_in){
		if ($this->read_through_mode || $this->read_only_mode){
			$value = $this->create_cache_content($key_in);
			$this->set($key_in, $value);
		}
	}
	
	function mcache($a_key_in){
		foreach ( $a_key_in as $key_in){
			$this->cache($key_in);
		}
	}
		
	# set will be allowed for both read-only mode
	#     and read-through mode		
	function set($key_in, $value, $expire=null){
		$dbh = @dba_open($this->data_file, 'cl', $this->dbm_handler_type); # open for read/write, plus lock
	
		if ($dbh === false){
			throw new Exception('dbm - Unable to open local dbm: '.$this->cache_name, 1112);
			return;
		}
		$key = $this->create_cache_key($key_in);
		$key_expire = $this->create_cache_key($key_in.'_expire');
		$value = json_encode($value);	# terry add

		if (dba_exists($key, $dbh)) {
			if (substr($this->dbm_handler_type, 0, 2) == 'db'){ # db* use replace
			#echo $this->dbm_handler_type.' type match';
				$flag = dba_replace($key, $value, $dbh);
				if(isset($expire)){
					$flag1 = dba_replace($key_expire,json_encode(time()+$expire), $dbh);
				}else{
					if($this->expire==0){
						$flag1 = dba_replace($key_expire,'-1', $dbh);
					}else{
						$flag1 = dba_replace($key_expire,json_encode(time()+$this->expire), $dbh);
					}
				}
				if ($flag === false || $flag1===false){
					throw new Exception("dbm - Can't write to database: ".$this->cache_name, 1113);
					return;
				}
			} else {
				dba_delete($key, $dbh);
				$flag = dba_insert($key,$value, $dbh);
				dba_delete($key_expire, $dbh);
				if(isset($expire)){
					$flag1 = dba_insert($key_expire,json_encode(time()+$expire), $dbh);
				}else{
					if($this->expire==0){
						$flag1 = dba_insert($key_expire,'-1', $dbh);
					}else{
						$flag1 = dba_insert($key_expire,json_encode(time()+$this->expire), $dbh);
					}
				}

				if ($flag === false || $flag1 === false){
					throw new Exception("dbm - Can't write to database: ".$this->cache_name, 1114);
					return;
				}
			}
			dba_close($dbh);
			return;
		}

		$flag = dba_insert($key,$value, $dbh);

		if(isset($expire)){
			$flag1 = dba_insert($key_expire,json_encode(time()+$expire), $dbh);
		}else{
			if($this->expire==0){
				$flag1 = dba_insert($key_expire,'-1', $dbh);
			}else{
				$flag1 = dba_insert($key_expire,json_encode(time()+$this->expire), $dbh);
			}
		}

		if ($flag === false || $flag1 === false){
			throw new Exception("dbm - Can't write to database: ".$this->cache_name, 1114);
			return;
		}
		dba_close($dbh);
	}
	
	
	function mset($h_value){
		foreach ($h_value as $key_in => $value){
			$this->set($key_in, $value);
		}
	}
	
	# read from gdbm only, it will not try to create one (unlike get) when no exists
	function read($key_in){
		$dbh = @dba_open($this->data_file,'cl', $this->dbm_handler_type);
		if ($dbh === false){
			#throw new Exception('dbm - Unable to open local kvcache - read: '.$this->cache_name, 1115);
			#return;
			dba_close($dbh);
			return '';
		}
		$key = $this->create_cache_key($key_in); 
		$value = dba_fetch($key, $dbh);
		dba_close($dbh);

		return json_decode($value,true);		# json_deocde terry add
	}
	
	
	/**
	 * read all as associative array
	 */
	function readall(){
    #echo "dbm - readall - this function is not implemented";
    #throw new Exception('dbm - readall - this function is not implemented: '.$this->cache_name, 1116);
	    return $this->fetch_assoc();
	}
	
	/* not a common support function, skip the implementation first
	 */
	//  private function fetch_assoc() {
	//    $dbh = dba_open($this->data_file,'rl', $this->dbm_handler_type);
	//	  $assoc = array();
	//    for($k = dba_firstkey($dbh); $k != false; $k = dba_nextkey($dbh)) {
	//    	#$key = $this->create_cache_key($key_in);
	//      $assoc[$k] = dba_fetch($k, $dbh);
	//    }
	//    return $assoc;
	//  } 	
	//	
	
	#function incr($key, $inc
	
	# $value : value to be increment
	# especially for increment nclick
	function incr($key_in, $value_inc){
		$value = $this->read($key_in);
		$this->set($key_in, $value + $value_inc);
	}

	function decr($key_in, $value_decr){
		$value = $this->read($key_in);
		$this->set($key_in, $value - $value_decr);
	}	
	
	function del($key_in){
		$dbh = @dba_open($this->data_file,'cl', $this->dbm_handler_type);
		$key = $this->create_cache_key($key_in);
		dba_delete($key , $dbh);
		dba_delete($key.'_expire', $dbh);			# delete expire too
	}

	function mdel($a_key_in){
		foreach ($a_key_in as $key_in){
			$this->del($key_in);
		}
	}   
  
	#
	#
	# to clear some gdbm, typical for clear it when type table is updated
	#
	function delete_all(){
	    echo "dbm - delete_all - this function is not implemented";
    	throw new Exception('dbm - delete_all - this function is not implemented: '.$this->cache_name, 1116);
	}

	function get_cache_status() {
		return $this->cache_status;
	}

	function __destruct() {
		if (method_exists($this, 'db_disconnect')){
			$this->db_disconnect();
		}
	}
	
	# terry add for handle start
	function create_cache_content($key_in){
		return $this->handle_obj->create_cache_content($key_in);
	}
	
	function post_format($key_in, $value){
		if(method_exists($this->handle_obj,'post_format')){
			return $this->handle_obj->post_format($key_in, $value);
		}else{
			return $value;
		}
	}

	# terry add for handle end
	
}
?>